#include "SyntaxTree.hpp"
#include "VirtualMachine.hpp"

namespace Welp
{
  SyntaxNode::~SyntaxNode()
  {
    for (size_t i = 0; i < mChildren.size(); ++i)
    {
      delete mChildren[i];
    }
  }

  Variant LiteralNode::Execute(VirtualMachine* vm, Variant* localsTable)
  {
    return mValue;
  }
  
  Variant VariableReferenceNode::Execute(VirtualMachine* vm, Variant* localsTable)
  {
    if (localsTable->HasValue(mName, &mLocation))
    {
      return localsTable->GetValue(mName, &mLocation);
    }
    else if (vm->mGlobalsTable.HasValue(mName, &mLocation))
    {
      return vm->mGlobalsTable.GetValue(mName, &mLocation);
    }
    
    throw Exception(Format("The variable '%s' could not be found in the locals or globals",
      mName.c_str()), ExceptionType::Runtime, &mLocation);
  }

  Variant VariableDeclarationNode::Execute(VirtualMachine* vm, Variant* localsTable)
  {
    // The initial value defaults to null (Variant default ctor)
    Variant initialValue;

    if (mOptionalInitialValue)
    {
      initialValue = mOptionalInitialValue->Execute(vm, localsTable);
    }

    if (mIsLocal)
    {
      localsTable->InsertOverwrite(Variant(mName), initialValue, &mLocation);
    }
    else
    {
      vm->mGlobalsTable.InsertOverwrite(Variant(mName), initialValue, &mLocation);
    }

    return initialValue;
  }

  Variant FunctionNode::Execute(VirtualMachine* vm, Variant* localsTable)
  {
    // A function node is not paired with a 'self' yet
    // That would happen via member access
    return Variant(this);
  }

  Variant ReturnNode::Execute(VirtualMachine* vm, Variant* localsTable)
  {
    ReturnException e;
    if (mOptionalReturnValue)
    {
      e.mResult = mOptionalReturnValue->Execute(vm, localsTable);
    }

    throw e;
  }

  Variant BreakNode::Execute(VirtualMachine* vm, Variant* localsTable)
  {
    throw BreakException();
  }

  Variant ContinueNode::Execute(VirtualMachine* vm, Variant* localsTable)
  {
    throw ContinueException();
  }

  Variant WhileNode::Execute(VirtualMachine* vm, Variant* localsTable)
  {
    // Evaluate the condition upon every loop
    while (mConditional->Execute(vm, localsTable).AsBool(&mLocation))
    {
      try
      {
        ExecuteAll(mStatements, vm, localsTable);
      }
      catch (BreakException)
      {
        break;
      }
      catch (ContinueException)
      {
        continue;
      }
    }

    return Variant();
  }

  Variant IfNode::Execute(VirtualMachine* vm, Variant* localsTable)
  {
    for (size_t i = 0; i < mParts.size(); ++i)
    {
      IfPartNode* part = mParts[i];
      bool wasExecuted = part->Execute(vm, localsTable).AsBool(&mLocation);

      if (wasExecuted)
        break;
    }

    return Variant();
  }

  Variant IfPartNode::Execute(VirtualMachine* vm, Variant* localsTable)
  {
    bool condition = true;

    if (mConditional)
    {
      condition = mConditional->Execute(vm, localsTable).AsBool(&mLocation);
    }

    if (condition)
    {
      ExecuteAll(mStatements, vm, localsTable);
    }

    return Variant(condition);
  }

  Variant UnaryOperatorNode::Execute(VirtualMachine* vm, Variant* localsTable)
  {
    throw Exception("NOT IMPLEMENTED", ExceptionType::Internal, &mLocation);
  }

  Variant BinaryOperatorNode::Execute(VirtualMachine* vm, Variant* localsTable)
  {
    // If this is an assignment, we need to execute the right first

    Variant lhs = mLeft->Execute(vm, localsTable);
    Variant rhs = mRight->Execute(vm, localsTable);

    switch (mOperator)
    {
    case TokenType::Plus:
      return Variant::Add(lhs, rhs, &mLocation);

    case TokenType::Minus:
      return Variant::Subtract(lhs, rhs, &mLocation);

    case TokenType::Multiply:
      return Variant::Multiply(lhs, rhs, &mLocation);

    case TokenType::Divide:
      return Variant::Divide(lhs, rhs, &mLocation);

    case TokenType::Modulo:
      return Variant::Modulo(lhs, rhs, &mLocation);
    }

    throw Exception("Invalid binary operator node (unsupported binary operator!)", ExceptionType::Internal, &mLocation);
  }

  Variant FunctionCallNode::Execute(VirtualMachine* vm, Variant* localsTable)
  {
    Variant callable = mCallable->Execute(vm, localsTable);

    vector<Variant> arguments;

    for (size_t i = 0; i < mArguments.size(); ++i)
    {
      ExpressionNode* argument = mArguments[i];
      arguments.push_back(argument->Execute(vm, localsTable));
    }

    return callable.Call(vm, arguments, &mLocation, localsTable);
  }
}

